/**
 * Copyright (c) 2009 Cerner Corporation.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Cerner Corporation - initial API and implementation
 */
package com.cerner.system.rest.internal.arguments;

import java.util.ArrayList;
import java.util.List;

/**
 * Internal helper class to validate method and constructor arguments.
 * 
 * @author Alex Horn
 */
public class Verifier {

  /**
   * A set of validators that constrain the possible argument values. It is only
   * referenced by the inner class
   * {@link ArgumentValueValidator#checkValues(Object...)}.
   */
  final Validator[] validators;

  /**
   * Instantiate an argument validation helper object with a custom set of
   * validators. This constructor allows for the injection of a non-empty array
   * of validators that constrain the possible argument values.
   */
  public Verifier(Validator validator, Validator... validators) {
    this.validators = new Validator[validators.length + 1];
    this.validators[0] = validator;
    System.arraycopy(validators, 0, this.validators, 1, validators.length);
  }

  /**
   * Map one argument name to its respective value in order to detect {@code
   * null} pointers.
   * 
   * @param name a single method argument name
   * @return object that allows the caller to map a single argument value to the
   *         provided argument name
   */
  public OneArgumentValueValidator names(String name) {
    return new OneArgumentValueValidator(arguments(name));
  }

  /**
   * Map two argument names to their respective values in order to determine if
   * there are any {@code null} pointers.
   * 
   * @param name0 name of the first method argument
   * @param name1 name of the second method argument
   * @return object that allows the caller to map a set of values to the provided
   *         argument names
   */
  public TwoArgumentValueValidator names(String name0, String name1) {
    return new TwoArgumentValueValidator(arguments(name0, name1));
  }

  /**
   * Map three argument names to their respective values in order to determine
   * if there are any {@code null} pointers.
   * 
   * @param name0 name of the first method argument
   * @param name1 name of the second method argument
   * @param name2 name of the third method argument
   * @return object that allows the caller to map a set of values to the provided
   *         argument names
   */
  public ThreeArgumentValueValidator names(String name0, String name1,
      String name2) {
    return new ThreeArgumentValueValidator(arguments(name0, name1, name2));
  }

  /**
   * Map a set of argument names to their respective values in order to
   * determine if there are any {@code null} pointers.
   * 
   * @param names an arbitrary number of method argument names
   * @return object that allows the caller to map a set of values to the provided
   *         argument names
   */
  public NArgumentValueValidator names(String... names) {
    return new NArgumentValueValidator(arguments(names));
  }

  private ArgumentValueValidator arguments(String... names) {
    return new ArgumentValueValidator(names);
  }

  /**
   * Strictly internal helper class for the {@code null} argument value
   * validation logic. This class should be aggregated in order implement a
   * {@code values(Object, Object, ...)} method that maps a set of argument
   * names to null argument values.
   * <p>
   * Preferably, the number of argument names matches the argument list of the
   * {@code values(...)} method. This is to avoid programming errors by allowing
   * name-value mapping compile-time checking.
   * 
   * @author Alex Horn
   */
  private final class ArgumentValueValidator {

    /**
     * Method argument names. Maintain primitive array data structure in order
     * to keep the overhead of checking method arguments to a minimum.
     */
    final String[] names;

    ArgumentValueValidator(String... names) {
      this.names = names;
    }

    /**
     * Check the method arguments for {@code null} values.
     * 
     * @param args method argument values
     * @throws IllegalArgumentException if one or multiple argument values are
     *           not conforming to the validator constraints
     */
    void checkValues(Object... args) {
      final StringBuilder message = new StringBuilder();
      for (Validator validator : Verifier.this.validators) {
        final List<String> errorNames = new ArrayList<String>(this.names.length);

        for (int i = 0; i < args.length && i < this.names.length; i++) {
          if (validator.canValidate(args[i])) {
            if (!validator.isValid(args[i])) {
              errorNames.add(this.names[i]);
            }
          }
        }

        if (!errorNames.isEmpty()) {
          if (message.length() > 0) {
            message.append(".\n");
          }
          message.append(validator.message(errorNames));
        }
      }
      if (message.length() > 0) {
        message.append(".");
        throw new IllegalArgumentException(message.toString());
      }
    }
  }

  /**
   * Map one method argument name to one single argument value when checking for
   * {@code null} values.
   */
  public static class OneArgumentValueValidator {

    private final ArgumentValueValidator validator;

    OneArgumentValueValidator(ArgumentValueValidator validator) {
      this.validator = validator;
    }

    public void values(Object arg) {
      this.validator.checkValues(arg);
    }

  }

  /**
   * Map two method argument names to two argument values when checking for
   * {@code null} values.
   */
  public static class TwoArgumentValueValidator {

    private final ArgumentValueValidator validator;

    TwoArgumentValueValidator(ArgumentValueValidator validator) {
      this.validator = validator;
    }

    public void values(Object arg0, Object arg1) {
      this.validator.checkValues(arg0, arg1);
    }

  }

  /**
   * Map three method argument names to three argument values when checking for
   * {@code null} values.
   */
  public static class ThreeArgumentValueValidator {

    private final ArgumentValueValidator validator;

    ThreeArgumentValueValidator(ArgumentValueValidator validator) {
      this.validator = validator;
    }

    public void values(Object arg0, Object arg1, Object arg2) {
      this.validator.checkValues(arg0, arg1, arg2);
    }

  }

  /**
   * Map an arbitrary number of method argument names to argument values when
   * checking for {@code null} values.
   */
  public static class NArgumentValueValidator {

    private final ArgumentValueValidator validator;

    NArgumentValueValidator(ArgumentValueValidator validator) {
      this.validator = validator;
    }

    public void values(Object... values) {
      this.validator.checkValues(values);
    }

  }
}
